/* Copyright (c) [2025] [zhi1208@126.com]
 * SPDX-License-Identifier: MIT
 */

/*********************
 *      INCLUDES
 *********************/
#include "lvgl.h"

#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"

#include "main.h"
#include "dma.h"
#include "tim.h"

#include "bsp_sw_i2c.h"
#include "bsp_delay_us.h"

/*********************
 *      DEFINES
 *********************/
#define LCD_H_RES 320
#define LCD_V_RES 480

#define LCD_CMD_ADDR (0x6c000000)
#define LCD_DATA_ADDR (0x6c000080)
#define LCD_CMD (*(volatile uint16_t *)LCD_CMD_ADDR)
#define LCD_DATA (*(volatile uint16_t *)LCD_DATA_ADDR)

#define BYTE_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void lvgl_handler(void *params);

static void lv_port_disp_init();
static void disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map);
static void dma_full_transfer_cb(DMA_HandleTypeDef *_hdma);

static void lv_port_indev_init();
static void touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data);

static void lcd_init();
static void lcd_on(bool on);
static void lcd_set_window(uint16_t startx, uint16_t starty, uint16_t endx, uint16_t endy);
static void lcd_clear(uint16_t color);

static void tp_sw_i2c_set_scl_with_delay(uint8_t value);
static void tp_sw_i2c_set_sda_with_delay(uint8_t value);
static uint8_t tp_sw_i2c_read_sda();
static uint8_t tp_write_data(sw_i2c_t *sw_i2c, uint16_t addr, uint8_t *data, uint32_t length);
static uint8_t tp_read_data(sw_i2c_t *sw_i2c, uint16_t addr, uint8_t *data, uint32_t length);
static void tp_init();
static uint8_t tp_is_pressed();
static void tp_get_point1(uint32_t *coordinatex, uint32_t *coordinatey);

static void encoder_init();
static void encoder_read(lv_indev_t *indev, lv_indev_data_t *data);

/**********************
 *  STATIC VARIABLES
 **********************/
static lv_display_t *disp;
static SemaphoreHandle_t lvgl_mutex;

static sw_i2c_t tp_sw_i2c = {.wr_addr = 0x28,
                             .rd_addr = 0x29,
                             .set_scl = tp_sw_i2c_set_scl_with_delay,
                             .set_sda = tp_sw_i2c_set_sda_with_delay,
                             .read_sda = tp_sw_i2c_read_sda};

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/
void bsp_lvgl_init()
{
    lv_init();

    lcd_init();
    tp_init();
    encoder_init();

    lv_tick_set_cb(HAL_GetTick);

    lv_port_disp_init();
    lv_port_indev_init();

    xTaskCreate(lvgl_handler, "lvgl", 2048, NULL, 25, NULL);
    lvgl_mutex = xSemaphoreCreateMutex();
    vQueueAddToRegistry(lvgl_mutex, "lvgl_mutex");
}

void bsp_lvgl_enter()
{
    xSemaphoreTake(lvgl_mutex, portMAX_DELAY);
}

void bsp_lvgl_exit()
{
    xSemaphoreGive(lvgl_mutex);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static void lvgl_handler(void *params)
{
    UNUSED(params);

    TickType_t xPreviousWakeTime = xTaskGetTickCount();
    while (1)
    {
        lv_timer_handler();
        vTaskDelayUntil(&xPreviousWakeTime, pdMS_TO_TICKS(5));
    }
}

static void lv_port_disp_init()
{
    disp = lv_display_create(LCD_H_RES, LCD_V_RES);
    lv_display_set_flush_cb(disp, disp_flush);

    LV_ATTRIBUTE_MEM_ALIGN
    static uint8_t buf_1[LCD_H_RES * 10 * BYTE_PER_PIXEL];

    LV_ATTRIBUTE_MEM_ALIGN
    static uint8_t buf_2[LCD_H_RES * 10 * BYTE_PER_PIXEL];
    lv_display_set_buffers(disp, buf_1, buf_2, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_PARTIAL);
}

static void disp_flush(lv_display_t *disp_drv, const lv_area_t *area, uint8_t *px_map)
{
    lcd_set_window(area->x1, area->y1, area->x2, area->y2);
    LCD_CMD = 0x2c;
    uint32_t px_cnt = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);
    HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, (uint32_t)px_map, LCD_DATA_ADDR, px_cnt);
}

static void dma_full_transfer_cb(DMA_HandleTypeDef *_hdma)
{
    lv_disp_flush_ready(disp);
}

static void lv_port_indev_init()
{
    lv_indev_t *indev_touchpad = lv_indev_create();
    lv_indev_set_type(indev_touchpad, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(indev_touchpad, touchpad_read);

    lv_indev_t *indev_encoder = lv_indev_create();
    lv_indev_set_type(indev_encoder, LV_INDEV_TYPE_ENCODER);
    lv_indev_set_read_cb(indev_encoder, encoder_read);

    lv_group_t *group = lv_group_create();
    lv_group_set_default(group);
    lv_indev_set_group(indev_encoder, group);
}

static void touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
    static int32_t last_x = 0;
    static int32_t last_y = 0;

    if (tp_is_pressed())
    {
        tp_get_point1((uint32_t *)&last_x, (uint32_t *)&last_y);
        data->state = LV_INDEV_STATE_PRESSED;
    }
    else
        data->state = LV_INDEV_STATE_RELEASED;

    data->point.x = last_x;
    data->point.y = last_y;
}

static void lcd_init()
{
    LCD_CMD = 0x11;

    HAL_Delay(120);

    LCD_CMD = 0x36;
    LCD_DATA = 0x08;

    LCD_CMD = 0x3A;
    LCD_DATA = 0x55;

    LCD_CMD = 0xF0;
    LCD_DATA = 0xC3;

    LCD_CMD = 0xF0;
    LCD_DATA = 0x96;

    LCD_CMD = 0xB4;
    LCD_DATA = 0x01;

    LCD_CMD = 0xB6;
    LCD_DATA = 0x0A;
    LCD_DATA = 0xA2;

    LCD_CMD = 0xB7;
    LCD_DATA = 0xC6;

    LCD_CMD = 0xB9;
    LCD_DATA = 0x02;
    LCD_DATA = 0xE0;

    LCD_CMD = 0xC0;
    LCD_DATA = 0x80;
    LCD_DATA = 0x16;

    LCD_CMD = 0xC1;
    LCD_DATA = 0x19;

    LCD_CMD = 0xC2;
    LCD_DATA = 0xA7;

    LCD_CMD = 0xC5;
    LCD_DATA = 0x16;

    LCD_CMD = 0xE8;
    LCD_DATA = 0x40;
    LCD_DATA = 0x8A;
    LCD_DATA = 0x00;
    LCD_DATA = 0x00;
    LCD_DATA = 0x29;
    LCD_DATA = 0x19;
    LCD_DATA = 0xA5;
    LCD_DATA = 0x33;

    LCD_CMD = 0xE0;
    LCD_DATA = 0xF0;
    LCD_DATA = 0x07;
    LCD_DATA = 0x0D;
    LCD_DATA = 0x04;
    LCD_DATA = 0x05;
    LCD_DATA = 0x14;
    LCD_DATA = 0x36;
    LCD_DATA = 0x54;
    LCD_DATA = 0x4C;
    LCD_DATA = 0x38;
    LCD_DATA = 0x13;
    LCD_DATA = 0x14;
    LCD_DATA = 0x2E;
    LCD_DATA = 0x34;

    LCD_CMD = 0xE1;
    LCD_DATA = 0xF0;
    LCD_DATA = 0x10;
    LCD_DATA = 0x14;
    LCD_DATA = 0x0E;
    LCD_DATA = 0x0C;
    LCD_DATA = 0x08;
    LCD_DATA = 0x35;
    LCD_DATA = 0x44;
    LCD_DATA = 0x4C;
    LCD_DATA = 0x26;
    LCD_DATA = 0x10;
    LCD_DATA = 0x12;
    LCD_DATA = 0x2C;
    LCD_DATA = 0x32;

    LCD_CMD = 0xF0;
    LCD_DATA = 0x3C;

    LCD_CMD = 0xF0;
    LCD_DATA = 0x69;

    HAL_Delay(120);

    LCD_CMD = 0x21;

    LCD_CMD = 0x29;

    lcd_clear(0);
    lcd_on(1);

    HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream0, HAL_DMA_XFER_CPLT_CB_ID, dma_full_transfer_cb);
}

static void lcd_on(bool on)
{
    if (on)
        HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_SET);
    else
        HAL_GPIO_WritePin(LCD_BL_GPIO_Port, LCD_BL_Pin, GPIO_PIN_RESET);
}

static void lcd_set_window(uint16_t startx, uint16_t starty, uint16_t endx, uint16_t endy)
{
    LCD_CMD = 0x2a;
    LCD_DATA = startx >> 8;
    LCD_DATA = startx & 0xff;
    LCD_DATA = endx >> 8;
    LCD_DATA = endx & 0xff;

    LCD_CMD = 0x2b;
    LCD_DATA = starty >> 8;
    LCD_DATA = starty & 0xff;
    LCD_DATA = endy >> 8;
    LCD_DATA = endy & 0xff;
}

static void lcd_clear(uint16_t color)
{
    uint32_t px_cnt = LCD_V_RES * LCD_H_RES;
    lcd_set_window(0, 0, LCD_H_RES - 1, LCD_V_RES - 1);
    LCD_CMD = 0x2c;
    for (uint32_t i = 0; i < px_cnt; ++i)
        LCD_DATA = color;
}

static void tp_sw_i2c_set_scl_with_delay(uint8_t value)
{
    HAL_GPIO_WritePin(TP_SCL_GPIO_Port, TP_SCL_Pin, (GPIO_PinState)value);
    bsp_delay_us(10);
}

static void tp_sw_i2c_set_sda_with_delay(uint8_t value)
{
    HAL_GPIO_WritePin(TP_SDA_GPIO_Port, TP_SDA_Pin, (GPIO_PinState)value);
    bsp_delay_us(10);
}

static uint8_t tp_sw_i2c_read_sda()
{
    return HAL_GPIO_ReadPin(TP_SDA_GPIO_Port, TP_SDA_Pin);
}

static uint8_t tp_write_data(sw_i2c_t *sw_i2c, uint16_t addr, uint8_t *data, uint32_t length)
{
    sw_i2c_start(sw_i2c);

    sw_i2c_send_byte(sw_i2c, sw_i2c->wr_addr);
    if (sw_i2c_recv_ack(sw_i2c))
        return 0;

    sw_i2c_send_byte(sw_i2c, addr >> 8);
    if (sw_i2c_recv_ack(sw_i2c))
        return 0;

    sw_i2c_send_byte(sw_i2c, addr & 0xff);
    if (sw_i2c_recv_ack(sw_i2c))
        return 0;

    for (uint32_t i = 0; i < length; ++i)
    {
        sw_i2c_send_byte(sw_i2c, data[i]);
        if (sw_i2c_recv_ack(sw_i2c))
            return 0;
    }

    sw_i2c_stop(sw_i2c);
    return 1;
}

static uint8_t tp_read_data(sw_i2c_t *sw_i2c, uint16_t addr, uint8_t *data, uint32_t length)
{
    sw_i2c_start(sw_i2c);

    sw_i2c_send_byte(sw_i2c, sw_i2c->wr_addr);
    if (sw_i2c_recv_ack(sw_i2c))
        return 0;

    sw_i2c_send_byte(sw_i2c, addr >> 8);
    if (sw_i2c_recv_ack(sw_i2c))
        return 0;

    sw_i2c_send_byte(sw_i2c, addr & 0xff);
    if (sw_i2c_recv_ack(sw_i2c))
        return 0;

    sw_i2c_start(sw_i2c);

    sw_i2c_send_byte(sw_i2c, sw_i2c->rd_addr);
    if (sw_i2c_recv_ack(sw_i2c))
        return 0;

    for (uint32_t i = 0; i < length - 1; ++i)
    {
        data[i] = sw_i2c_recv_byte(sw_i2c);
        sw_i2c_send_ack(sw_i2c, 0);
    }
    data[length - 1] = sw_i2c_recv_byte(sw_i2c);
    sw_i2c_send_ack(sw_i2c, 1);

    sw_i2c_stop(sw_i2c);

    return 1;
}

static void tp_init()
{
    HAL_GPIO_WritePin(TP_RST_GPIO_Port, TP_RST_Pin, GPIO_PIN_RESET);
    HAL_Delay(10);
    HAL_GPIO_WritePin(TP_RST_GPIO_Port, TP_RST_Pin, GPIO_PIN_SET);
}

static uint8_t tp_is_pressed()
{
    uint8_t data = 0;
    uint8_t temp = 0;
    tp_read_data(&tp_sw_i2c, 0x814e, &data, 1);

    if (data & 0x80)
        tp_write_data(&tp_sw_i2c, 0x814e, &temp, 1);

    if (data & 0x0f)
        return 1;

    return 0;
}

static void tp_get_point1(uint32_t *coordinatex, uint32_t *coordinatey)
{
    uint8_t data[4];
    tp_read_data(&tp_sw_i2c, 0x8150, data, 4);
    *coordinatex = ((uint16_t)data[1] << 8) + data[0];
    *coordinatey = ((uint16_t)data[3] << 8) + data[2];
}

static void encoder_init()
{
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
}

static void encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data)
{
    int16_t cnt = __HAL_TIM_GetCounter(&htim3);
    data->enc_diff = cnt;
    __HAL_TIM_SetCounter(&htim3, 0);

    if (!HAL_GPIO_ReadPin(ENCODER_KEY_GPIO_Port, ENCODER_KEY_Pin))
    {
        vTaskDelay(20);
        if (!HAL_GPIO_ReadPin(ENCODER_KEY_GPIO_Port, ENCODER_KEY_Pin))
            data->state = LV_INDEV_STATE_PRESSED;
        else
            data->state = LV_INDEV_STATE_RELEASED;
    }
    else
        data->state = LV_INDEV_STATE_RELEASED;
}
